package jsmile.utils;

import java.util.Arrays;
import java.util.Collection;

import smile.Network;

public class JSmileUtil {

	public static String dbnNodeId(String nodeName, int t) {
		if (t > 0)
			return nodeName + "_" + t;
		else
			return nodeName;
	}

	/**
	 * Uses convex smoothing so that there wont be any zero probabilities. It
	 * makes the distribution more uniform. <br>
	 * P' = alpha*U + (1-alpha)P <br>
	 * where U is uniform distr
	 * 
	 * @param net
	 */
	public static void smoothProbabilities(Network net, double alpha) {
		// TODO without this "smile.SMILEException: Value not valid - node XYZ"
		// was thrown
		net.updateBeliefs();

		final double oneMinAlpha = 1 - alpha;

		for (String node : net.getAllNodeIds()) {
			smoothNodeDefinition(net, node, alpha);
		}
		/*
		 * for (int nodeHandle : net.getAllNodes()) { //int outcomes =
		 * net.getNodeValue(nodeHandle).length; double[] probs =
		 * net.getNodeDefinition(nodeHandle); double uniform = alpha /
		 * probs.length;
		 * 
		 * // smooth definition for each combination of parent probabilities for
		 * (int i = 0; i < probs.length; i++) { probs[i] = uniform + oneMinAlpha
		 * * probs[i]; }
		 * 
		 * net.setNodeDefinition(nodeHandle, probs); }
		 */
	}

	public static void smoothNodeDefinition(Network net, String node,
			double alpha) {
		final double oneMinAlpha = 1 - alpha;

		double[] probs = net.getNodeDefinition(node);
		int outcomes = net.getNodeValue(node).length / net.getSliceCount();
		double uniform = alpha / outcomes;

		// smooth definition for each combination of parent probabilities
		// atemporal
		net.setNodeDefinition(
				node,
				fixAndsmoothDefinition(net.getNodeDefinition(node), uniform,
						oneMinAlpha, outcomes));
		// TODO smooth temporal
		for (int i = 1; i <= net.getMaxNodeTemporalOrder(node); i++) {

			net.setNodeTemporalDefinition(
					node,
					i,
					fixAndsmoothDefinition(net.getNodeTemporalDefinition(node, i),
							uniform, oneMinAlpha, outcomes));
		}
	}

	protected static double[] fixAndsmoothDefinition(double[] probs, double uniform,
			double oneMinAlpha, int outcomes) {
		return smoothDefinition(fixIncompleteDefinition(probs, outcomes), uniform, oneMinAlpha);
	}
	
	
	protected static double[] smoothDefinition(double[] probs, double uniform,
			double oneMinAlpha) {
		for (int i = 0; i < probs.length; i++) {
			probs[i] = uniform + oneMinAlpha * probs[i];
		}
		return probs;
	}

	/**
	 * Fix definition that contains probability distributions that do not sum to 1.
	 * @param probs
	 * @return
	 */
	protected static double[] fixIncompleteDefinition(double[] probs, int outcomes) {
		final double uniform = 1d/outcomes;
		
		for (int i = 0; i < probs.length % outcomes; i++) {
			double sum = 0;
			for(int j = i*outcomes; j < (i+1)*outcomes; i++) {
				sum += probs[j];
			}
			
			if(sum < 0.9) {
				// corrupted distribution, fix it to be at least uniform
				Arrays.fill(probs, i*outcomes, (i+1)*outcomes, uniform);
			}
		}
		return probs;
	}

	
	/**
	 * Adds temporal node to a network.
	 * 
	 * @param net
	 * @param name
	 */
	public static void addTemporalNode(Network net, String name) {
		net.addNode(Network.NodeType.Cpt, name);
		net.setNodeTemporalType(name, Network.NodeTemporalType.Plate);
	}

	/**
	 * 
	 * @param net
	 *            Network where the node will be added
	 * @param name
	 *            Name of the node
	 * @param outcomes
	 *            Collection of outcome/state names
	 */
	public static void addTemporalNode(Network net, String name,
			Collection<String> outcomes) {
		addTemporalNode(net, name);
		addNodeOutcomes(net, name, outcomes);
		net.deleteOutcome(name, 0);
		net.deleteOutcome(name, 0);
	}

	/**
	 * Add multiple outcomes at once.
	 * 
	 * @param net
	 * @param name
	 * @param outcomes
	 */
	public static void addNodeOutcomes(Network net, String name,
			Collection<String> outcomes) {
		for (String outcome : outcomes) {
			net.addOutcome(name, outcome);
		}
	}

	/**
	 * Sets uniform prior for a node.
	 * 
	 * @param net
	 * @param node
	 */
	public static void setUniformPrior(Network net, String node) {
		int outcomes = net.getOutcomeCount(node);
		double[] probs = new double[outcomes];
		Arrays.fill(probs, 1.0 / outcomes);
		net.setNodeDefinition(node, probs);
	}
}
